package com.starleague.hexagonal;

import java.util.Collection;
import java.util.Map;

import com.google.common.base.Preconditions;
import com.starleague.common.AbstractHasInfoProperties;

public abstract class AbstractHexGrid
        extends AbstractHasInfoProperties
        implements IHexGrid
{
    private int minGridX;
    private int minGridY;
    private int numHexesX;
    private int numHexesY;

    private AbstractHex[][] hexArray2D;

    protected AbstractHexGrid(int minGridX,
                              int minGridY,
                              int numHexesX,
                              int numHexesY)
    {
        // TODO: Check these here or in the controller? Write a test for this!
        Preconditions.checkArgument((numHexesX > 0), "Invalid numHexesX [{}]", numHexesX);
        Preconditions.checkArgument((numHexesY > 0), "Invalid numHexesY [{}]", numHexesY);

        this.minGridX = minGridX;
        this.minGridY = minGridY;
        this.numHexesX = numHexesX;
        this.numHexesY = numHexesY;

        this.hexArray2D = new AbstractHex[this.numHexesX][this.numHexesY];
    }

    protected AbstractHexGrid()
    {
        //
    }

    @Override
    public int getMinGridX()
    {
        return this.minGridX;
    }

    protected void setMinGridX(int minGridX)
    {
        this.minGridX = minGridX;
    }

    @Override
    public int getMinGridY()
    {
        return this.minGridY;
    }

    protected void setMinGridY(int minGridY)
    {
        this.minGridY = minGridY;
    }

    @Override
    public int getNumHexesX()
    {
        return this.numHexesX;
    }

    protected void setNumHexesX(int numHexesX)
    {
        this.numHexesX = numHexesX;
    }

    @Override
    public int getNumHexesY()
    {
        return this.numHexesY;
    }

    protected void setNumHexesY(int numHexesY)
    {
        this.numHexesY = numHexesY;
    }

    @Override
    public AbstractHex getHexAt(IHexCoordinate hexCoordinate) throws HexOutOfBoundsException
    {
        int arrayX = hexCoordinate.getGridX() - this.minGridX;
        int arrayY = hexCoordinate.getGridY() - this.minGridY;

        if ((arrayX < 0) || (arrayY < 0) || (arrayX >= this.numHexesX) || (arrayY >= this.numHexesY))
        {
            throw new HexOutOfBoundsException(this, hexCoordinate);
        }

        return this.hexArray2D[arrayX][arrayY];
    }

    // @SuppressWarnings("unchecked")
    // void addHex(HexDao<?> hex) throws HexException
    // {
    // setHex((HEX) hex);
    // // this.getHexList().add((HEX) hex);
    // }

    protected void setHexes(Collection<? extends AbstractHex> hexes) throws HexException
    {
        this.hexArray2D = new AbstractHex[this.numHexesX][this.numHexesY];
        for (AbstractHex hex : hexes)
        {
            setHex(hex);
        }
    }

    protected void setHex(AbstractHex hex) throws HexException
    {
        IHexCoordinate hexCoordinate = hex.getHexCoordinate();

        int arrayX = hexCoordinate.getGridX() - this.minGridX;
        int arrayY = hexCoordinate.getGridY() - this.minGridY;

        if ((arrayX < 0) || (arrayY < 0) || (arrayX >= this.numHexesX) || (arrayY >= this.numHexesY))
        {
            throw new HexOutOfBoundsException(this, hexCoordinate);
        }

        // Make sure the hex at the given location doesn't already exist.
        AbstractHex oldHex = this.hexArray2D[arrayX][arrayY];
        // if ((oldHex != null) && (oldHex != hex))
        if (oldHex != null)
        {
            throw new HexException("[" + hexCoordinate + "] already has a Hex:" + oldHex);
        }

        this.hexArray2D[arrayX][arrayY] = hex;
    }

    @Override
    public Map<String, Object> infoProperties()
    {
        Map<String, Object> infoPropertyMap = super.infoProperties();
        infoPropertyMap.put("minGridX", this.minGridX);
        infoPropertyMap.put("minGridY", this.minGridY);
        infoPropertyMap.put("numHexesX", this.numHexesX);
        infoPropertyMap.put("numHexesY", this.numHexesY);
        return infoPropertyMap;
    }
}
